
#include "bezier.h"

#include <cassert>
#include <cmath>
#include "coord.h"

const static int BEZIER_GRADO = 3;

Bezier::Bezier(const std::vector<Coord> &pcontrol) : pcontrol(pcontrol) {
   int puntos = pcontrol.size();

   assert(Bezier::satisfacePrecond(puntos));   //cant == (n*grado) + 1

   this->longitudSegmento = 1.0 / int(puntos / BEZIER_GRADO);
}

Bezier::Bezier(const Bezier& aCopiar) {
   this->pcontrol = aCopiar.pcontrol;
   this->longitudSegmento = aCopiar.longitudSegmento;
}


/*
 * Retorna true si
 *    cant > 0
 *  y
 *    cant % grado() == 1
 *
 * sino, false.
 * */
bool Bezier::satisfacePrecond(size_t cant) {
   return (cant > 0) and ((cant % BEZIER_GRADO) == 1);
}

size_t Bezier::grado() const {
   return size_t(BEZIER_GRADO);
}

Bezier::~Bezier() {  }

Coord Bezier::centro() const {
   Coord c;
   int puntos = pcontrol.size();

   for(int i = 0; i < puntos; ++i){
      c += pcontrol[i];
   }

   return c * (1.0/puntos);
}
/*
 * Retorna una coordenada de la curva, en funcion del parametro i
 *
 * Precondicion:
 *    0 <= i <= 1
 * */
Coord Bezier::operator()(float i) const {
   float u;
   int j;

   this->parametros(i, u, j);

   Coord resultado;

   //"u" es el parametro "local" entre (0  1)
   //"j" es la seleccion del segmento bezier de puntos (j, j+1, j+2, ... j+grado)

   float acum = 0.0;
   for(int numBase = 0; numBase < BEZIER_GRADO + 1; ++j, ++numBase){
      float base = coefBase(u, numBase);
      acum += base;

      resultado += pcontrol[j] * base;
   }

   assert(fabs(acum - 1.0) < 0.001);
   return resultado;
}

/*
 * Retorna una coordenada que representa un vector tangente a la curva, 
 * en funcion del parametro i
 *
 * Precondicion:
 *    0 <= i <= 1
 * */
Coord Bezier::tangente(float i) const {
   float u;
   int j;

   this->parametros(i, u, j);

   Coord resultado;

   if(u < 0 + 0.0001){
      u += 0.0001;

   }else if( u > 1 - 0.0001) {
      u -= 0.0001;
   }

   //"u" es el parametro "local" entre (0  1)
   //"j" es la seleccion del segmento bezier de puntos (j, j+1, j+2, ... j+grado)

   for(int numBase = 0; numBase < BEZIER_GRADO + 1; ++j, ++numBase){
      float base = coefBaseDerivada(u, numBase);

      resultado += pcontrol[j] * base;
   }

   return resultado;
}

float Bezier::coefBase(float u, int numBase) const {
   return combinatorio(numBase) * pow(u, numBase) * pow(1 - u, BEZIER_GRADO - numBase);
}

float Bezier::coefBaseDerivada(float u, int numBase) const {
   const int i = numBase;
   const int n = BEZIER_GRADO;

   return combinatorio(i) * ((i*pow(u, i-1)*pow(1-u, n-i)) - (pow(u, i)*(n-i)*pow(1-u, n-i-1)));
}

/*
 * Determina, en funcion del parametro i que parametro local u discretizado
 * le corresponde y que puntos de control p[j], p[j+1], ... seran
 * utilizados en el calculo de la coordenada segun i.
 *
 * Precondicion:
 *    0 <= i <= 1
 * */
void Bezier::parametros(float i, float &u, int &j) const {
   assert(i >= 0);
   assert(i <= 1.0 + 0.001);

   Coord resultado;

   int numSegmento = int(i / longitudSegmento);

   if(numSegmento == int(pcontrol.size() / BEZIER_GRADO)) {
      --numSegmento;
   }

   j = BEZIER_GRADO * numSegmento;
   u = (i / longitudSegmento) - numSegmento;
}


const std::vector<Coord>& Bezier::getPuntosControl() const {
   return this->pcontrol;
}

Combinatorio Bezier::combinatorio(BEZIER_GRADO);

